package com.fmi.chesstern;

import java.util.ArrayList;

import com.fmi.chesstern.gameboard.GameBoard;
import com.fmi.chesstern.gameboard.GameField;
import com.fmi.chesstern.interpreter.ParseObject;
import com.fmi.chesstern.interpreter.Parser;
import com.fmi.chesstern.interpreter.ParseObject.Action;
import com.fmi.chesstern.pieces.Bishop;
import com.fmi.chesstern.pieces.ChessPiece;
import com.fmi.chesstern.pieces.ChessPiece.PieceColor;
import com.fmi.chesstern.pieces.ChessPiece.PieceType;
import com.fmi.chesstern.pieces.King;
import com.fmi.chesstern.pieces.Knight;
import com.fmi.chesstern.pieces.Pawn;
import com.fmi.chesstern.pieces.Queen;
import com.fmi.chesstern.pieces.Rook;

public class ChessGame {

	private static ChessGame instance = null;
	private GameBoard board = null;
	private PieceColor turnColor = null;
	private GameStatus gameStatus = null;

	private ArrayList<Memento> mementoStates;

	protected ChessGame() {
		resetBoard();
	}
	
	public void executeCommand(String command) {
		ParseObject parseObject = Parser.parseCommand(command);
		if (parseObject.getAction() == Action.UNKNOWN) {
			//TODO warn user
		} else if (parseObject.getAction() == Action.UNDO) {
			undo();
			//TODO notify user
		} else if (parseObject.getAction() == Action.FORFEIT) {
			forfeit(turnColor);
			//TODO notify user
		} else if (parseObject.getAction() == Action.MOVE) {
			GameField start = extractGameField(parseObject.getParameters().get(0));
			GameField goal  = extractGameField(parseObject.getParameters().get(1));
			if (move(start, goal) == MoveStatus.OK) {
				//TODO notify user
			} else {
				//TODO warn user
			}
		}
	}
	
	private GameField extractGameField(Object object) {
		char letter = ((String)object).charAt(0);
		int rowIndex = Integer.parseInt((((String)object).substring(1)));
		int colIndex = 0;
		switch (letter) {
		case 'a': colIndex = 0; break;
		case 'b': colIndex = 1; break;
		case 'c': colIndex = 2; break;
		case 'd': colIndex = 3; break;
		case 'e': colIndex = 4; break;
		case 'f': colIndex = 5; break;
		case 'g': colIndex = 6; break;
		case 'h': colIndex = 7; break;
		}
		return new GameField(rowIndex, colIndex);
	}

	public void undo() {
		if (mementoStates.size() > 0) {
			Memento memento = mementoStates.get(mementoStates.size() - 1);
			mementoStates.remove(memento);
			memento.restore();
			turnColor = PieceColor.getOppositeColor(turnColor);
			updateGameStatus();
		}
	}
	
	public void resetBoard() {
		board = new GameBoard();
		gameStatus = GameStatus.NORMAL;
		setTurnColor(PieceColor.WHITE);
		mementoStates = new ArrayList<Memento>();
		resetWhitePieces();
		resetBlackPieces();
	}
	
	public void updateGameStatus() {
		if (checkForCheck(this.board, turnColor)) {
			if (turnColor == PieceColor.WHITE) {
				gameStatus = GameStatus.WHITE_CHECK;
			} else {
				gameStatus = GameStatus.BLACK_CHECK;
			}
			if (checkForCheckMate(this.board, turnColor)) {
				if (turnColor == PieceColor.WHITE) {
					gameStatus = GameStatus.WHITE_CHECKMATE;
				} else {
					gameStatus = GameStatus.BLACK_CHECKMATE;
				}
			}
		} else if (checkForStaleMate(this.board, turnColor)) {
			gameStatus = GameStatus.STALEMATE;
		} else {
			gameStatus = GameStatus.NORMAL;
		}
	}
	
	private boolean checkForStaleMate(GameBoard board, PieceColor color) {
		boolean staleMate = true;
		for(String field : board.getPieces().keySet()) {
			ChessPiece piece = board.getPieces().get(field);
			if (piece != null && piece.getColor() == color) {
				if (piece.getValidMoves(new GameField(field), board).size() > 0) {
					staleMate = false;
					break;
				}
			}
		}
		return staleMate;
	}
	
	private boolean checkForCheckMate(GameBoard board, PieceColor color) {
		boolean checkMate = false;
		if (color == null) {
			color = turnColor;
		}
		if (checkForCheck(board, color)) {
			GameField kingPosition = getKingPosition(board, color);
			ChessPiece king = board.getPieces().get(kingPosition.toString());
			if (king.getValidMoves(kingPosition, board).size() == 0) {
				checkMate = true;
			}
		}
		return checkMate;
	}
	
	private boolean checkForCheck(GameBoard board, PieceColor color) {
		boolean check = false;
		if (color == null) {
			color = turnColor;
		}
		GameField kingPosition = getKingPosition(board, color);
		for(String field : board.getPieces().keySet()) {
			ChessPiece piece = board.getPieces().get(field);
			if (piece != null && piece.getColor() != color) {
				if (piece.getValidMoves(new GameField(field), board).contains(kingPosition)) {
					check = true;
					break;
				}
			}
		}
		return check;
	}
	
	private GameField getKingPosition(GameBoard board, PieceColor color) {
		GameField kingPosition = null;
		for (String field : board.getPieces().keySet()) {
			ChessPiece piece = board.getPieces().get(field);
			if (piece != null && piece.getType() == PieceType.KING && piece.getColor() == color) {
				kingPosition = new GameField(field);
				break;
			}
		}
		return kingPosition;
	}

	public void forfeit(PieceColor color) {
		if (color == PieceColor.WHITE) {
			setGameStatus(GameStatus.WHITE_CHECKMATE);
		} else {
			setGameStatus(GameStatus.BLACK_CHECKMATE);
		}
	}
	
	private void resetWhitePieces() {
		//add pawns
		for (int i = 0; i < 8; i++) {
			board.addPiece(new Pawn(PieceColor.WHITE), new GameField(6, i));
		}
		//add rooks
		board.addPiece(new Rook(PieceColor.WHITE), new GameField(7, 0));
		board.addPiece(new Rook(PieceColor.WHITE), new GameField(7, 7));
		//add knights
		board.addPiece(new Knight(PieceColor.WHITE), new GameField(7, 1));
		board.addPiece(new Knight(PieceColor.WHITE), new GameField(7, 6));
		//add bishops
		board.addPiece(new Bishop(PieceColor.WHITE), new GameField(7, 2));
		board.addPiece(new Bishop(PieceColor.WHITE), new GameField(7, 5));
		//add queen
		board.addPiece(new Queen(PieceColor.WHITE), new GameField(7, 3));
		//add king
		board.addPiece(new King(PieceColor.WHITE), new GameField(7, 4));
	}
	
	private void resetBlackPieces() {
		// add pawns
		for (int i = 0; i < 8; i++) {
			board.addPiece(new Pawn(PieceColor.BLACK), new GameField(1, i));
		}
		// add rooks
		board.addPiece(new Rook(PieceColor.BLACK), new GameField(0, 0));
		board.addPiece(new Rook(PieceColor.BLACK), new GameField(0, 7));
		// add knights
		board.addPiece(new Knight(PieceColor.BLACK), new GameField(0, 1));
		board.addPiece(new Knight(PieceColor.BLACK), new GameField(0, 6));
		// add bishops
		board.addPiece(new Bishop(PieceColor.BLACK), new GameField(0, 2));
		board.addPiece(new Bishop(PieceColor.BLACK), new GameField(0, 5));
		// add queen
		board.addPiece(new Queen(PieceColor.BLACK), new GameField(0, 3));
		// add king
		board.addPiece(new King(PieceColor.BLACK), new GameField(0, 4));
	}
	
	public MoveStatus canMove(GameField start, GameField goal) {
		MoveStatus status = MoveStatus.ILLEGAL;
		ChessPiece piece = board.getPieces().get(start.toString());
		if (piece != null) {
			if (piece.getColor() == turnColor) {
				ArrayList<GameField> allMoves = piece.getValidMoves(start, board);
				if (allMoves.contains(goal)) {
					status = isMoveSafe(start, goal, piece.getColor());
				}
			} else {
				status = MoveStatus.OPPONENT_TURN;
			}
		} else {
			status = MoveStatus.NO_PIECE;
		}
		return status;
	}
	
	private MoveStatus isMoveSafe(GameField start, GameField goal, PieceColor color) {
		MoveStatus status = MoveStatus.OK;
		GameBoard tempBoard = new GameBoard(board);
		tempBoard.movePiece(start, goal);
		if (checkForCheck(tempBoard, color)) {
			status = MoveStatus.KING_EXPOSED;
		}
		return status;
	}
	
	public MoveStatus move(GameField start, GameField goal) {
		MoveStatus status = canMove(start, goal);
		if (status == MoveStatus.OK) {
			mementoStates.add(new Memento(board));
			board.movePiece(start, goal);
			turnColor = PieceColor.getOppositeColor(turnColor);
			updateGameStatus();
		}
		return status;
	}
	
	public static ChessGame getInstance() {
		if (instance == null) {
			instance = new ChessGame();
		}
		return instance;
	}
	
	public GameBoard getBoard() {
		return board;
	}

	public void setBoard(GameBoard board) {
		this.board = board;
	}
	
	public PieceColor getTurnColor() {
		return turnColor;
	}

	public void setTurnColor(PieceColor turnColor) {
		this.turnColor = turnColor;
	}
	
	public GameStatus getGameStatus() {
		return gameStatus;
	}

	public void setGameStatus(GameStatus gameStatus) {
		this.gameStatus = gameStatus;
	}
	
	public enum MoveStatus {
		OK, ILLEGAL, OPPONENT_TURN, KING_EXPOSED, NO_PIECE;
	}
	
	public enum GameStatus {
		NORMAL, WHITE_CHECK, BLACK_CHECK, WHITE_CHECKMATE, BLACK_CHECKMATE, STALEMATE;
	}
}
